#region License

// This file is part of Grawlix.
// 
// Grawlix is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 3 of
// the License, or (at your option) any later version.
// Grawlix is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public
// License along with Grawlix.  If not, see
// <http://www.gnu.org/licenses/>.

#endregion

using System;
using System.Collections.Generic;
using System.IO;
using C5;

namespace Grawlix.Services.Resources
{
	/// <summary>
	/// The default implementation of a file stream resolver.
	/// </summary>
	public class FileStreamResourceResolver : IUriResolver
	{
		/// <summary>
		/// The base URI to complete URIs with.
		/// </summary>
		private static readonly Uri _baseUri =
			new Uri(new Uri(Uri.UriSchemeFile + "://"),
			        new Uri(AppDomain.CurrentDomain.BaseDirectory));

		/// <summary>
		/// The set of paths to search for resources.
		/// </summary>
		private readonly ArrayList<Uri> _searchPaths =
			new ArrayList<Uri>();

		/// <summary>
		/// Initializes a new instance of the 
		/// <see cref="FileStreamResourceResolver"/> class.
		/// </summary>
		/// <param name="searchPaths">The paths to search for
		/// resources.</param>
		/// <exception cref="ArgumentNullException"><c>searchPaths</c>
		/// is null.</exception>
		/// <exception cref="ArgumentException">One of the search paths
		/// is null.</exception>
		/// <exception cref="ArgumentException">No search paths were
		/// specified.</exception>
		public FileStreamResourceResolver(
			IEnumerable<string> searchPaths)
		{
			if (searchPaths == null)
				throw new ArgumentNullException("searchPaths");
			foreach (string path in searchPaths)
			{
				if (path == null)
				{
					throw new ArgumentException(
						"One of the search paths is null.", "searchPaths");
				}
				_searchPaths.Add(new Uri(_baseUri,
				                         Path.GetFullPath(path)));
			}
			if (_searchPaths.IsEmpty)
			{
				throw new ArgumentException(
					"No search paths were specified.", "searchPaths");
			}
		}

		/// <summary>
		/// Completes the given uri with the default scheme.
		/// </summary>
		/// <param name="uri">The URI to complete. Can be absolute
		/// (will have no effect).</param>
		/// <returns>A completed URI.</returns>
		/// <remarks>Returns one of the given paths.</remarks>
		public Uri CompleteUri(Uri uri)
		{
			return new Uri(_baseUri, uri);
		}

		/// <summary>
		/// Determines whether the specified scheme is supported.
		/// </summary>
		/// <param name="scheme">The URI scheme to test.</param>
		/// <returns>
		/// 	<c>true</c> if the specified scheme is supported;
		/// 	otherwise, <c>false</c>.
		/// </returns>
		public bool IsSupported(string scheme)
		{
			return scheme == Uri.UriSchemeFile;
		}

		/// <summary>
		/// Loads an embedded resource or Stream by its URI.
		/// </summary>
		/// <param name="uri">The URI that identifies the embedded
		/// resource.</param>
		/// <returns>The embedded resource or Stream found.</returns>
		/// <exception cref="IOException">A generic I/O error occurred.
		/// </exception>
		/// <exception cref="FileNotFoundException">The URI could not
		/// be resolved or does not point to an embedded resource.
		/// </exception>
		/// <remarks>This is a blocking operation which should only be
		/// called asynchronously.</remarks>
		/// <exception cref="ArgumentException">The URI scheme is not
		/// supported.</exception>
		public object Load(Uri uri)
		{
			object result = TryLoad(uri);
			if (result != null)
				return result;
			throw new FileNotFoundException(
				"A request was made to load a stream with " +
				"universal resource identifier (" + uri + "), " +
				"but no such resource was found. " +
				"The resource is not necessarily a critical component, " +
				"but its data will not be accessible.");
		}

		/// <summary>
		/// Tries to load an embedded resource or Stream by its URI.
		/// </summary>
		/// <param name="uri">The URI that identifies the embedded
		/// resource.</param>
		/// <returns>
		/// The embedded resource or Stream found, or null if none
		/// found.
		/// </returns>
		/// <exception cref="IOException">A generic I/O error occurred.
		/// </exception>
		/// <remarks>This is a blocking operation which should only be
		/// called asynchronously.</remarks>
		/// <exception cref="ArgumentException">The URI scheme is not
		/// supported.</exception>
		/// <exception cref="ArgumentNullException"><c>uri</c> is null.
		/// </exception>
		public object TryLoad(Uri uri)
		{
			if (uri == null)
				throw new ArgumentNullException("uri");
			if (!IsSupported(uri.Scheme))
			{
				throw new ArgumentException(
					"unsupported scheme: " + uri.Scheme, "uri");
			}
			uri = new Uri(uri.AbsolutePath.TrimStart('/'), UriKind.Relative);
			foreach (var resourcePath in _searchPaths)
			{
				try
				{
					var world = new Uri(resourcePath, uri);
					return new FileStream(world.LocalPath, FileMode.Open);
				}
				catch (FileNotFoundException)
				{
				}
				catch (DirectoryNotFoundException)
				{
				}
			}
			return null;
		}
	}
}